// KenEgozi.com - my blog engine
// This project is licensed under the MIT License
//
//Copyright (c) 2007, Ken Egozi
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.

//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.

// This class is somewhat based on the article "CAPTCHA Image" from
// CodeProject.com, on http://www.codeproject.com/aspnet/CaptchaImage.asp


using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

namespace KenEgozi.Com.Weblog.Application
{
	/// <summary>
	/// Used to generate captha images
	/// </summary>
	public class CaptchaImage : IDisposable
	{
		private readonly string _text;
		private int _width;
		private int _height;
		private string _familyName;
		private Bitmap _image;

		public string Text { get { return _text; } }
		public Bitmap Image { get { return _image; } }
		public int Width { get { return _width; } }
		public int Height { get { return _height; } }

		private readonly Random random = new Random();
		private bool disposed = false;

		/// <summary>
		/// Initializes a new instance of the CaptchaImage class using the
		/// specified text, width and height.
		/// </summary>
		/// <param name="text">The text in to be generated</param>
		/// <param name="width">Image width in pixels</param>
		/// <param name="height">Image height in pixels</param>
		public CaptchaImage(string text, int width, int height)
		{
			_text = text;
			SetDimensions(width, height);
			GenerateImage();
		}

		/// <summary>
		/// Initializes a new instance of the CaptchaImage class using the
		/// specified text, width, height and font-family.
		/// </summary>
		/// <param name="text">The text in to be generated</param>
		/// <param name="width">Image width in pixels</param>
		/// <param name="height">Image height in pixels</param>
		/// <param name="familyName">Font family</param>
		public CaptchaImage(string text, int width, int height, string familyName)
		{
			_text = text;
			SetDimensions(width, height);
			SetFamilyName(familyName);
			GenerateImage();
		}

		/// <summary>
		/// Finalizer. Cleanup initiated by the framework
		/// </summary>
		~CaptchaImage()
		{
			Dispose(false);
		}


		/// <summary>
		/// Disposer. Cleanup initiated by client's code
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Cleanup
		/// </summary>
		/// <param name="disposing">True when called from a client code, false when called by the framework</param>
		protected virtual void Dispose(bool disposing)
		{
			if (!disposed)
				if (disposing)
					_image.Dispose();
			disposed = true;
		}

		private void SetDimensions(int width, int height)
		{
			// Check the width and height.
			if (width <= 0)
				throw new ArgumentOutOfRangeException("width", width, "Argument out of range, must be greater than zero.");
			if (height <= 0)
				throw new ArgumentOutOfRangeException("height", height, "Argument out of range, must be greater than zero.");
			_width = width;
			_height = height;
		}

		private void SetFamilyName(string familyName)
		{
			// If the named font is not installed, default to a system font.
			Font font = null;
			try
			{
				font = new Font(familyName, 12F);
				_familyName = familyName;
			}
			catch (Exception)
			{
				_familyName = FontFamily.GenericSerif.Name;
			}
			finally
			{
				if (font!=null)
					font.Dispose();
			}
		}

		private void GenerateImage()
		{
			// Create a new 32-bit bitmap image.
			Bitmap bitmap = new Bitmap(_width, _height, PixelFormat.Format32bppArgb);

			// Create a graphics object for drawing.
			Graphics g = Graphics.FromImage(bitmap);
			g.SmoothingMode = SmoothingMode.AntiAlias;
			Rectangle rect = new Rectangle(0, 0, _width, _height);

			// Fill in the background.
			HatchBrush hatchBrush = new HatchBrush(HatchStyle.SmallConfetti, Color.LightGray, Color.White);
			g.FillRectangle(hatchBrush, rect);

			// Set up the text font.
			SizeF size;
			float fontSize = rect.Height + 1;
			Font font;
			// Adjust the font size until the text fits within the image.
			do
			{
				fontSize--;
				font = new Font(_familyName, fontSize, FontStyle.Bold);
				size = g.MeasureString(_text, font);
			} while (size.Width > rect.Width);

			// Set up the text format.
			StringFormat format = new StringFormat();
			format.Alignment = StringAlignment.Center;
			format.LineAlignment = StringAlignment.Center;

			// Create a path using the text and warp it randomly.
			GraphicsPath path = new GraphicsPath();
			path.AddString(_text, font.FontFamily, (int)font.Style, font.Size, rect, format);
			float v = 4F;
			PointF[] points =
			{
				new PointF(random.Next(rect.Width) / v, random.Next(rect.Height) / v),
				new PointF(rect.Width - random.Next(rect.Width) / v, random.Next(rect.Height) / v),
				new PointF(random.Next(rect.Width) / v, rect.Height - random.Next(rect.Height) / v),
				new PointF(rect.Width - random.Next(rect.Width) / v, rect.Height - random.Next(rect.Height) / v)
			};
			Matrix matrix = new Matrix();
			matrix.Translate(0F, 0F);
			path.Warp(points, rect, matrix, WarpMode.Perspective, 0F);

			// Draw the text.
			hatchBrush = new HatchBrush(HatchStyle.LargeConfetti, Color.LightGray, Color.DarkGray);
			g.FillPath(hatchBrush, path);

			// Add some random noise.
			int m = Math.Max(rect.Width, rect.Height);
			for (int i = 0; i < (int)(rect.Width * rect.Height / 30F); i++)
			{
				int x = random.Next(rect.Width);
				int y = random.Next(rect.Height);
				int w = random.Next(m / 50);
				int h = random.Next(m / 50);
				g.FillEllipse(hatchBrush, x, y, w, h);
			}

			// Clean up.
			font.Dispose();
			hatchBrush.Dispose();
			g.Dispose();

			// Set the image.
			_image = bitmap;
		}
	}
}
